#include "murmurhash3.h"
#include <cstring>

namespace MurmurHash3 {

inline uint64_t rotl64(uint64_t x, int8_t r) {
    return (x << r) | (x >> (64 - r));
}

inline uint64_t fmix64(uint64_t k) {
    k ^= k >> 33;
    k *= 0xff51afd7ed558ccd;
    k ^= k >> 33;
    k *= 0xc4ceb9fe1a85ec53;
    k ^= k >> 33;
    return k;
}

void MurmurHash3_x64_64(const void* key, int len, uint32_t seed, void* out) {
    const uint8_t* data = static_cast<const uint8_t*>(key);
    uint64_t h = seed ^ (len * c1);

    const uint64_t* blocks = reinterpret_cast<const uint64_t*>(data);
    for (int i = 0; i < len / 8; ++i) {
        uint64_t k = blocks[i];
        k *= c1;
        k = rotl64(k, 31);
        k *= c2;
        h ^= k;
        h = rotl64(h, 27);
        h = h * 5 + 0x52dce729;
    }

    const uint8_t* tail = data + (len / 8) * 8;
    uint64_t k = 0;
    switch (len & 7) {
        case 7: k ^= uint64_t(tail[6]) << 48;
        case 6: k ^= uint64_t(tail[5]) << 40;
        case 5: k ^= uint64_t(tail[4]) << 32;
        case 4: k ^= uint64_t(tail[3]) << 24;
        case 3: k ^= uint64_t(tail[2]) << 16;
        case 2: k ^= uint64_t(tail[1]) << 8;
        case 1: k ^= uint64_t(tail[0]);
                 k *= c1;
                 k = rotl64(k, 31);
                 k *= c2;
                 h ^= k;
    }

    h ^= len;
    h = fmix64(h);

    *reinterpret_cast<uint64_t*>(out) = h;
}

void MurmurHash3_x64_128(const void* key, int len, uint32_t seed, void* out) {
    const uint8_t* data = static_cast<const uint8_t*>(key);
    const int nblocks = len / 16;
    uint64_t h1 = seed;
    uint64_t h2 = seed;

    const uint64_t* blocks = reinterpret_cast<const uint64_t*>(data);
    for (int i = 0; i < nblocks; ++i) {
        uint64_t k1 = blocks[i * 2];
        uint64_t k2 = blocks[i * 2 + 1];

        k1 *= c1;
        k1 = rotl64(k1, 31);
        k1 *= c2;
        h1 ^= k1;

        h1 = rotl64(h1, 27);
        h1 += h2;
        h1 = h1 * 5 + 0x52dce729;

        k2 *= c2;
        k2 = rotl64(k2, 33);
        k2 *= c1;
        h2 ^= k2;

        h2 = rotl64(h2, 31);
        h2 += h1;
        h2 = h2 * 5 + 0x38495ab5;
    }

    const uint8_t* tail = data + nblocks * 16;
    uint64_t k1 = 0;
    uint64_t k2 = 0;
    switch (len & 15) {
        case 15: k2 ^= uint64_t(tail[14]) << 48;
        case 14: k2 ^= uint64_t(tail[13]) << 40;
        case 13: k2 ^= uint64_t(tail[12]) << 32;
        case 12: k2 ^= uint64_t(tail[11]) << 24;
        case 11: k2 ^= uint64_t(tail[10]) << 16;
        case 10: k2 ^= uint64_t(tail[9]) << 8;
        case 9:  k2 ^= uint64_t(tail[8]);
                 k2 *= c2;
                 k2 = rotl64(k2, 33);
                 k2 *= c1;
                 h2 ^= k2;
        case 8:  k1 ^= uint64_t(tail[7]) << 56;
        case 7:  k1 ^= uint64_t(tail[6]) << 48;
        case 6:  k1 ^= uint64_t(tail[5]) << 40;
        case 5:  k1 ^= uint64_t(tail[4]) << 32;
        case 4:  k1 ^= uint64_t(tail[3]) << 24;
        case 3:  k1 ^= uint64_t(tail[2]) << 16;
        case 2:  k1 ^= uint64_t(tail[1]) << 8;
        case 1:  k1 ^= uint64_t(tail[0]);
                 k1 *= c1;
                 k1 = rotl64(k1, 31);
                 k1 *= c2;
                 h1 ^= k1;
    }

    h1 ^= len;
    h2 ^= len;

    h1 += h2;
    h2 += h1;

    reinterpret_cast<uint64_t*>(out)[0] = fmix64(h1);
    reinterpret_cast<uint64_t*>(out)[1] = fmix64(h2);
}

} // namespace MurmurHash3
